package org.beanscreator.parsers;

import org.beanscreator.definitionreader.ClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.management.modelmbean.XMLParseException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;

public class MyDOMParser implements Parser {

    private static final Logger LOGGER = LoggerFactory.getLogger(MyDOMParser.class);

    private static final String INVALID_TAG_OR_ATRIBUTE_EX = "Invalid tag or atribute n XML!";
    public static final String SCHEMA_LANG = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
    public static final String SPRING_BEANS_SCHEMA = "http://www.springframework.org/schema/beans";

    @Override
    public List<ClassDefinition> parse(String xmlFilePath, boolean validate) throws XMLParseException {

        List<ClassDefinition> classDefsList = new LinkedList<>();

        Document document = createDocumentFromXML(xmlFilePath, validate);

        NodeList nodeList = document.getDocumentElement().getChildNodes();

        for (int i = 0; i < nodeList.getLength(); i++) {

            Node node = nodeList.item(i);
            if (node instanceof Element) {
                ClassDefinition def = new ClassDefinition();
                def.setId(
                        node.getAttributes().getNamedItem("id").getNodeValue());
                def.setFullClassName(
                        node.getAttributes().getNamedItem("class").getNodeValue());

                NodeList childNodes = node.getChildNodes();
                for (int j = 0; j < childNodes.getLength(); j++) {

                    Node cNode = childNodes.item(j);

                    if (cNode instanceof Element) {

                        Node n = cNode;
                        NamedNodeMap nodeMap = n.getAttributes();

                        switch (cNode.getNodeName()) {
                            case "constructor-arg":

                                addConstructorArgFromNode(def, nodeMap);
                                break;
                            case "property":

                                addParamValueFromNode(def, nodeMap);
                                break;
                            default:

                                throw new XMLParseException(INVALID_TAG_OR_ATRIBUTE_EX);
                        }
                    }
                }
                classDefsList.add(def);
            }

        }

        return classDefsList;
    }

    private Document createDocumentFromXML(String filePath, boolean validate)
            throws XMLParseException {

        DocumentBuilderFactory factory =
                DocumentBuilderFactory.newInstance();

        if (validate) {
            factory.setValidating(true);
            factory.setNamespaceAware(true);
            factory.setAttribute(SCHEMA_LANG, SPRING_BEANS_SCHEMA);
        }

        //Get the DOM Builder
        DocumentBuilder builder = null;

        Document document = null;
        try (InputStream is = new FileInputStream(filePath)) {

            builder = factory.newDocumentBuilder();
            builder.setErrorHandler(new SimpleErrorHandler());
            document = builder.parse(is);

        } catch (IOException | ParserConfigurationException
                | SAXException e) {

            LOGGER.error("", e);
            throw new XMLParseException(e.toString());
        }

        return document;
    }

    private void addConstructorArgFromNode(ClassDefinition def, NamedNodeMap nodeMap) {
        String val = nodeMap.getNamedItem("value").getNodeValue();
        def.addConstructorArgVal(val);
    }

    private void addParamValueFromNode(ClassDefinition def, NamedNodeMap nodeMap) {
        String argName = nodeMap.getNamedItem("name").getNodeValue();
        Node argValNode = nodeMap.getNamedItem("value");
        Object argVal;

        if (argValNode != null) {

            argVal = argValNode.getNodeValue();
        } else {

            argValNode = nodeMap.getNamedItem("ref");
            argVal = argValNode.getNodeValue();
            argVal = "$" + argVal;
        }

        def.addParamValue(argName, argVal);
    }
}
